#include <stdio.h>
#include <ctype.h>
#include "simpio.h"
#include "commands.h"
#include "scanadt.h"
#include "genlib.h"
#include "strlib.h"
#include "eval.h"
#include "syntaxparser.h"
#include "symtab.h"
#include "parser.h"

#define START 1

typedef void (*commandFnT)(string);

typedef struct {
	commandFnT fn;
} *commandEntryT;

//Commands
static void funcLIST(string line);
static void funcHELP(string line);
static void funcRUN(string line);

//Statements
static void funcLET(string line);
static void funcINPUT(string line);
static void funcPRINT(string line);
static void funcGOTO(string line);
static void funcIF(string line);

static void funcEND(string line);
static void funcREM(string line);

static symtabADT commandTable;
static void DefineCommand(string cmd, commandFnT fn);

static int getNextLine(int currentLine);

void parseCommand(scannerADT scanner, string line){
	string token;
	int lineNumber;

	token = ReadToken(scanner);
	if(!StringEqual(token,"")){
		if (isdigit(token[0])) {
			lineNumber = StringToInteger(token);
			if(MoreTokensExist(scanner)){
				if(lineNumber <= NUMBER_OF_LINES && lineNumber > 0)
					program[lineNumber] = line;
			}
			else 
				program[lineNumber] = "NULL";
		} else {
			line = ConvertToUpperCase(line);
			token = ConvertToUpperCase(token);
			if(StringEqual(token, "QUIT")){
				if(MoreTokensExist(scanner)) Error("End of line expected");
				exit(0);
			}else 
				ExecuteCommand(token, line);
		}
	}
}

void funcRUN(string line){
	if(!StringEqual("RUN",line)) Error("End of line expected");

	PC = getNextLine(START);
	while(PC < NUMBER_OF_LINES){
		runLine(program[PC]);
		PC = getNextLine(PC+1);
	}
}

static int getNextLine(int currentLine){
	int i;

	for(i = currentLine; i <= NUMBER_OF_LINES; i++){
		if(!StringEqual(program[i], "NULL")){
			return i;
		}
	}
	return NUMBER_OF_LINES+1;
}

static void funcLIST(string line){
	int i;
	if(!StringEqual("LIST",line)) Error("End of line expected");
	for(i = 1; i <= NUMBER_OF_LINES; i++){
		if(!StringEqual(program[i],"NULL")){
			printf("%s\n",program[i]);
		}
	}
}

static void funcHELP(string line){
	if(!StringEqual("HELP",line)) Error("End of line expected");
	printf("This program implements a minimal basic interpreter.\n\n");
	printf("Commands:\n");
	printf("RUN          -- Runs current program\n");
	printf("LIST         -- Lists the program\n");
	printf("HELP         -- This message\n");
	printf("QUIT         -- Exits from interpreter\n\n");
	printf("Statements:\n");
	printf("REM . . .    -- Remark\n");
	printf("LET v = exp  -- Assignment\n");
	printf("INPUT v      -- Reads value from user\n");
	printf("PRINT exp    -- Prints value of expression\n");
	printf("GOTO n       -- Goes to line n\n");
	printf("IF c THEN n  -- Goes to line n if c is TRUE\n");
}

void InitCommandTable(void){
	commandTable = NewSymbolTable();
	//Commands
	DefineCommand("RUN", funcRUN);
	DefineCommand("HELP", funcHELP);
	DefineCommand("LIST", funcLIST);
	//Statements
	DefineCommand("LET", funcLET);
	DefineCommand("INPUT", funcINPUT);
	DefineCommand("PRINT", funcPRINT);
	DefineCommand("GOTO", funcGOTO);
	DefineCommand("IF", funcIF);
	DefineCommand("END", funcEND);
	DefineCommand("REM", funcEND);
}

static void DefineCommand(string cmd, commandFnT fn){
	commandEntryT entry;
	entry = New(commandEntryT);
	entry->fn = fn;
	Enter(commandTable, cmd, entry);
}

void ExecuteCommand(string cmd, string line){
	commandEntryT entry;
	entry = (commandEntryT) Lookup(commandTable, cmd);
	if (entry == UNDEFINED) {
		printf("Undefined command: %s\n", cmd);
		return;
	}
	entry->fn(line);
}

static void funcLET(string line){
	string variable;
	int value;
	expressionADT exp;
	scannerADT scanner;

	scanner = NewScanner();
	SetScannerSpaceOption(scanner, IgnoreSpaces);
	SetScannerString(scanner, line);

	if(!StringEqual(ReadToken(scanner), "LET")) ReadToken(scanner); //Used to get rid of lineNr and LET

	variable = ReadToken(scanner);
	if(!isalpha(variable[0])) Error("%s cannot appear on the left side of =", variable);

	if(!StringEqual(ReadToken(scanner), "=")) Error("Expected a =");
	exp = ParseExp(scanner);
	value = EvalExp(exp);

	SetIdentifierValue(variable, value);
}

static void funcINPUT(string line){
	string variable; 
	int value;
	scannerADT scanner;

	scanner = NewScanner();
	SetScannerSpaceOption(scanner, IgnoreSpaces);
	SetScannerString(scanner, line);

	if(!isdigit(ReadToken(scanner)[0])) Error("Statement legal only in a program");
	ReadToken(scanner);	//"INPUT"
	variable = ReadToken(scanner);

	printf(" ? ");
	value = GetInteger();

	SetIdentifierValue(variable, value);
}

static void funcPRINT(string line){
	string variable;
	scannerADT scanner;
	expressionADT exp;

	scanner = NewScanner();
	SetScannerSpaceOption(scanner, IgnoreSpaces);
	SetScannerString(scanner, line);

	if(!StringEqual(ReadToken(scanner), "PRINT")) ReadToken(scanner); //Used to get rid of lineNr and PRINT

	exp = ParseExp(scanner);
	printf("%d\n", EvalExp(exp));
}

static void funcGOTO(string line){
	string token;
	scannerADT scanner = NewScanner();

	SetScannerSpaceOption(scanner, IgnoreSpaces);

	SetScannerString(scanner, line);
	if(!isdigit(ReadToken(scanner)[0])) Error("Statement legal only in a program");
	ReadToken(scanner); //"GOTO"

	token = ReadToken(scanner);
	if(!isdigit(token[0])) Error("Line number must be numeric");
	PC = StringToInteger(token)-1;
}

static void funcIF(string line){
	string exp1, exp2, tempStr, prec, token;
	expressionADT expression1, expression2;
	scannerADT scanner, scanExp1, scanExp2;
	scanner = NewScanner();
	scanExp1 = NewScanner();
	scanExp2 = NewScanner();
	SetScannerSpaceOption(scanner, IgnoreSpaces);
	SetScannerSpaceOption(scanExp1, IgnoreSpaces);
	SetScannerSpaceOption(scanExp2, IgnoreSpaces);

	SetScannerString(scanner, line);
	if(!isdigit(ReadToken(scanner)[0])) Error("Statement legal only in a program"); //LineNr
	ReadToken(scanner); //IF

	exp1 = "";
	exp2 = "";

	while(TRUE){
		tempStr = ReadToken(scanner);
		if(StringEqual(tempStr, "=") || StringEqual(tempStr, "<") || StringEqual(tempStr, ">")) break;
		exp1 = Concat(exp1, tempStr);
	}
	prec = tempStr;
	while(TRUE){
		tempStr = ReadToken(scanner);
		if(StringEqual(tempStr, "THEN")) break;
		exp2 = Concat(exp2, tempStr);
	}

	SetScannerString(scanExp1, exp1);
	SetScannerString(scanExp2, exp2);

	expression1 = ParseExp(scanExp1);
	expression2 = ParseExp(scanExp2);

	token = ReadToken(scanner);
	if(!isdigit(token[0])) Error("Line number must be numeric");

	switch(prec[0]) {
	case '=':
		if(EvalExp(expression1) == EvalExp(expression2)) 
			PC = StringToInteger(token)-1;
		break;
	case '<':
		if(EvalExp(expression1) < EvalExp(expression2)) 
			PC = StringToInteger(token)-1;
		break;
	case '>':
		if(EvalExp(expression1) > EvalExp(expression2)) 
			PC = StringToInteger(token)-1;
		break;
	}
}

static void funcEND(string line){
	PC = NUMBER_OF_LINES;
}

static void funcREM(string line){}
